'*******************************************************************************
' *  
' *  Product: GSALib
' *  Description: A C# API for accessing the Google Search Appliance.
' *
' *  (c) Copyright 2008 Michael Cizmar + Associates Ltd.  (MC+A)
' *  
'*******************************************************************************


Imports System
Imports System.Collections.Generic
Imports System.Text
Imports System.IO
Imports System.Xml
Imports System.Collections
Imports Gsalibvb.GSALib.Constants
Imports Gsalibvb.GSALib.GSA

Namespace GSALib.Utils
    ''' <summary>
    ''' Class provides Methods to Parse Stream to XML supported in GSA
    ''' <para>Author Albert Ghukasyan</para>    
    ''' </summary>
    Friend NotInheritable Class XMLParser
#Region "Variables"

        Private spelling As Spelling
        Private currSuggestion As Suggestion
        Private response As Response
        Private currResult As Result
        Private currKeyMatch As KeyMatches
        Private currOneBoxResponse As OneBoxResponse
        Private currOneBoxResult As OneBoxResult
        Private contentBuff As StringBuilder
        Private resultsList As ArrayList
        Private keyMatchasList As ArrayList
        Private xmlTags As XMLTags
        Private inSpelling As Boolean = False
        Private inSynonyms As Boolean = False
        Private inResult As Boolean = False
        Private inResponse As Boolean = False
        Private inOneBoxResult As Boolean = False
        Private inOneBoxResponse As Boolean = False
        Private inKeyMatches As Boolean = False
        Public Const LineEnd As String = "" & Chr(10) & ""
        Public sb As New StringBuilder()
        Private stack As New Stack()

#End Region

#Region "Conctructor"

        Friend Sub New()
            response = New Response()
            contentBuff = New StringBuilder()
            resultsList = New ArrayList()
            keyMatchasList = New ArrayList()
            currResult = New Result()
            currKeyMatch = New KeyMatches()
            currOneBoxResponse = New OneBoxResponse()
            currOneBoxResult = New OneBoxResult()
            xmlTags = New XMLTags()
        End Sub

#End Region

#Region "Methods"

        Public Function getGSAResponse() As Response
            Return Me.response
        End Function

        ''' <summary>
        ''' Parses Stream to GSA Xml format
        ''' </summary>
        ''' <param name="stream">Stream from which need to parse</param>
        Public Sub Parse(ByVal stream As TextReader)
            Try
                Dim reader As New XmlTextReader(stream)
                While reader.Read()
                    Select Case reader.NodeType
                        Case XmlNodeType.XmlDeclaration
                            XmlDeclaration(reader.Name, reader.Value)
                            Exit Select
                        Case XmlNodeType.Element
                            Dim namespaceURI As String = reader.NamespaceURI
                            Dim name As String = reader.Name
                            Dim attributes As New Hashtable()
                            If reader.HasAttributes Then
                                For i As Integer = 0 To reader.AttributeCount - 1
                                    reader.MoveToAttribute(i)
                                    attributes.Add(reader.Name, reader.Value)
                                Next
                            End If
                            StartElement(namespaceURI, name, name, attributes)
                            Exit Select
                        Case XmlNodeType.EndElement
                            EndElement(reader.NamespaceURI, reader.Name, reader.Name)
                            Exit Select
                        Case XmlNodeType.Text
                            characterBufferAppend(reader.Value, 0, reader.Value.Length)
                            Exit Select
                        Case XmlNodeType.Whitespace
                            Exit Select
                        Case Else
                            Exit Select
                    End Select
                End While
            Catch e As XmlException
                Throw e
            End Try
        End Sub

        Private Sub XmlDeclaration(ByVal sName As String, ByVal value As String)
            Emit(sb.ToString() + "<?" + sName + " " + value + " ?>" + LineEnd)
        End Sub

        Private Sub StartElement(ByVal namespaceURI As String, ByVal sName As String, ByVal qName As String, ByVal attrs As Hashtable)
            Dim Attributes As String = ""
            For Each key As Object In attrs.Keys
                Attributes += " " + key.ToString() + "=""" + attrs(key).ToString() + """"
            Next
            stack.Push(New EmitObject(sName, "<" + sName + Attributes + ">", "<" + sName + Attributes + " />"))
            Emit(sb.ToString() + "  <" + sName + Attributes + ">" + LineEnd)
            StartElement(namespaceURI, sName, qName, attrs)
        End Sub

        Public Sub parseStartElement(ByVal uri As String, ByVal localName As String, ByVal qName As String, ByVal attributes As Hashtable)
            Dim tag As Integer = xmlTags.getTag(qName)
            inKeyMatches = inKeyMatches OrElse (tag = xmlTags.GM)
            inResponse = inResponse OrElse (tag = xmlTags.RES)
            ' inside response element
            inResult = inResponse AndAlso (inResult OrElse (tag = xmlTags.R))
            ' inside results element
            inOneBoxResponse = inOneBoxResponse OrElse (tag = xmlTags.OBRES)
            inOneBoxResult = inOneBoxResponse AndAlso (inOneBoxResult OrElse (tag = xmlTags.ENTOBRESULTS))
            ' inside oneBox results element
            inSpelling = inSpelling OrElse (tag = xmlTags.Spelling)
            Select Case tag
                Case xmlTags.RES
                    Dim startIndex As String = IIf(attributes("SN") Is Nothing, "1", attributes("SN").ToString())
                    Dim endIndex As String = IIf(attributes("EN") Is Nothing, "1", attributes("EN").ToString())
                    response.setStartIndex(IIf(startIndex IsNot Nothing, Long.Parse(startIndex), 1))
                    response.setEndIndex(IIf(endIndex IsNot Nothing, Long.Parse(endIndex), 1))
                    Exit Select
                Case xmlTags.R
                    Dim mimeType As String = IIf(attributes("MIME") Is Nothing, "", attributes("MIME").ToString())
                    Dim indentation As String = IIf(attributes("L") Is Nothing, "1", attributes("L").ToString())
                    currResult.setIndentation(IIf(indentation Is Nothing, 1, Int32.Parse(indentation)))
                    currResult.setMimeType(mimeType)
                    Exit Select
                Case xmlTags.PARAM
                    Dim name As String = IIf(attributes("name") Is Nothing, "", attributes("name").ToString())
                    Dim value As String = IIf(attributes("value") Is Nothing, "", attributes("value").ToString())
                    response.putParam(name, value)
                    Exit Select
                Case xmlTags.C
                    If inResult Then
                        Dim cid As String = IIf(attributes("CID") Is Nothing, "", attributes("CID").ToString())
                        Dim size As String = IIf(attributes("SZ") Is Nothing, "", attributes("SZ").ToString())
                        Dim encoding As String = IIf(attributes("ENC") Is Nothing, "", attributes("ENC").ToString())
                        If encoding Is Nothing OrElse "".Equals(encoding.Trim()) Then
                            encoding = "UTF-8"
                        End If
                        currResult.setCacheDocEncoding(encoding)
                        currResult.setCacheDocId(cid)
                        currResult.setCacheDocSize(size)
                    End If
                    Exit Select
                Case xmlTags.FS
                    Dim fieldName As String = IIf(attributes("NAME") Is Nothing, "", attributes("NAME").ToString())
                    Dim fieldValue As String = IIf(attributes("VALUE") Is Nothing, "", attributes("VALUE").ToString())
                    currResult.addField(fieldName, fieldValue)
                    Exit Select
                Case xmlTags.MT
                    Dim metaName As String = IIf(attributes("N") Is Nothing, "", attributes("N").ToString())
                    Dim metaValue As String = IIf(attributes("V") Is Nothing, "", attributes("V").ToString())
                    currResult.addMeta(metaName, metaValue)
                    Exit Select
                Case xmlTags.Spelling
                    inSpelling = True
                    spelling = New Spelling()
                    Exit Select
                Case xmlTags.Suggestion
                    If inSpelling Then
                        currSuggestion = New Suggestion()
                        currSuggestion.setText(IIf(attributes("q") Is Nothing, "", attributes("q").ToString()))
                    End If
                    Exit Select
                Case xmlTags.Synonyms
                    inSynonyms = True
                    Exit Select
                Case xmlTags.GM
                    inKeyMatches = True
                    Exit Select
                Case xmlTags.OneSynonym
                    If inSynonyms Then
                        response.addSynonymWithMarkup(IIf(attributes("q") Is Nothing, "", attributes("q").ToString()))
                    End If
                    Exit Select
            End Select
            clearContent()
        End Sub

        Private Sub EndElement(ByVal namespaceURI As String, ByVal sName As String, ByVal qName As String)
            Me.EndElement(namespaceURI, sName, qName)
            Dim lastPushed As EmitObject = TryCast(stack.Peek(), EmitObject)
            If lastPushed Is Nothing Then
                Return
            End If

            If lastPushed.Name = sName Then
                stack.Pop()
                Emit(sb.ToString() + "</" + sName + ">" + LineEnd)
            Else
                lastPushed = TryCast(stack.Pop(), EmitObject)
                While lastPushed.Name <> sName
                    sb = sb.Replace(lastPushed.Expression, lastPushed.ReplaceExpression)
                    lastPushed = TryCast(stack.Pop(), EmitObject)
                End While
                Emit(sb.ToString() + "</" + sName + ">" + LineEnd)
            End If
        End Sub

        Public Sub parseEndElement(ByVal uri As String, ByVal localName As String, ByVal qName As String)
            Dim tag As Integer = xmlTags.getTag(qName)
            If inOneBoxResult Then
                doOneBoxResult(tag)
            ElseIf inOneBoxResponse Then
                doOneBoxResponse(tag)
            ElseIf inResult Then
                doResult(tag)
            ElseIf inResponse Then
                doResponse(tag)
            ElseIf inSpelling Then
                doSpelling(tag)
            ElseIf inSynonyms Then
                doSynonym(tag)
            ElseIf inKeyMatches Then
                doKeyMatch(tag)
            Else
                doTopLevel(tag)
            End If
        End Sub

        Private Sub Characters(ByVal buf As String, ByVal offset As Integer, ByVal len As Integer)
            buf = buf.Replace("&", "&amp;").Replace("<", "&lt;").Replace(">", "&gt;").Replace("'", "&apos;").Replace("\", "&quot;")
            If buf.Length <> len Then
                len = buf.Length
            End If
            sb.Append(buf + LineEnd)
            Me.characterBufferAppend(buf, offset, len)
        End Sub

        Public Sub characterBufferAppend(ByVal ch As String, ByVal start As Integer, ByVal length As Integer)
            contentBuff.Append(ch, start, length)
        End Sub

        Private Sub clearContent()
            If contentBuff.Length > 0 Then
                contentBuff.Remove(0, contentBuff.Length)
            End If
        End Sub

        Private Sub doResult(ByVal tag As Integer)
            Select Case tag
                Case XMLTags.R
                    resultsList.Add(currResult)
                    currResult = New Result()
                    inResult = False
                    Exit Select
                Case XMLTags.U
                    currResult.setUrl(contentBuff.ToString())
                    Exit Select
                Case XMLTags.UE
                    currResult.setEscapedUrl(contentBuff.ToString())
                    Exit Select
                Case XMLTags.T
                    currResult.setTitle(contentBuff.ToString())
                    Exit Select
                Case XMLTags.RK
                    currResult.setRating(Int32.Parse(contentBuff.ToString()))
                    Exit Select
                Case XMLTags.S
                    currResult.setSummary(contentBuff.ToString())
                    Exit Select
                Case XMLTags.LANG
                    currResult.setLanguage(contentBuff.ToString())
                    Exit Select
                Case XMLTags.CRAWLDATE
                    currResult.setCrawelDate(contentBuff.ToString())
                    Exit Select
            End Select
        End Sub

        Private Sub doSynonym(ByVal tag As Integer)
            inSynonyms = (tag <> XMLTags.Synonyms)
        End Sub

        Private Sub doSpelling(ByVal tag As Integer)
            Select Case tag
                Case XMLTags.Spelling
                    response.setSpelling(spelling)
                    inSpelling = False
                    Exit Select
                Case XMLTags.Suggestion
                    currSuggestion.setTextMarkup(contentBuff.ToString())
                    spelling.addSuggestion(currSuggestion)
                    Exit Select
            End Select
        End Sub

        Private Sub doResponse(ByVal tag As Integer)
            Select Case tag
                Case XMLTags.RES
                    response.setResults(resultsList)
                    inResponse = False
                    Exit Select
                Case XMLTags.M
                    response.setNumResults(Long.Parse(contentBuff.ToString()))
                    Exit Select
                Case XMLTags.FI
                    response.setFiltered(True)
                    Exit Select
                Case XMLTags.PU
                    response.setPreviousResponseUrl(contentBuff.ToString())
                    Exit Select
                Case XMLTags.NU
                    response.setNextResponseUrl(contentBuff.ToString())
                    Exit Select
            End Select
        End Sub

        Private Sub doTopLevel(ByVal tag As Integer)
            Select Case tag
                Case XMLTags.TM
                    response.setSearchTime([Double].Parse(contentBuff.ToString()))
                    Exit Select
                Case XMLTags.Q
                    response.setQuery(contentBuff.ToString())
                    Exit Select
                Case XMLTags.GM
                    response.setKeyMatches(keyMatchasList)
                    Exit Select
                Case XMLTags.CT
                    response.setSearchComments(contentBuff.ToString())
                    Exit Select

            End Select
        End Sub

        Private Sub doKeyMatch(ByVal tag As Integer)
            Select Case tag
                Case XMLTags.GM
                    keyMatchasList.Add(currKeyMatch)
                    currKeyMatch = New KeyMatches()
                    inKeyMatches = False
                    response.setKeyMatches(keyMatchasList)
                    Exit Select
                Case XMLTags.GL
                    currKeyMatch.setURL(contentBuff.ToString())
                    Exit Select
                Case XMLTags.GD
                    currKeyMatch.setDescription(contentBuff.ToString())
                    Exit Select
            End Select
        End Sub

        Private Sub doOneBoxResult(ByVal tag As Integer)
            Select Case tag
                Case XMLTags.ENTOBRESULTS
                    currOneBoxResponse.addResult(currOneBoxResult)
                    currOneBoxResult = New OneBoxResult()
                    inOneBoxResult = False
                    Exit Select
                Case XMLTags.U
                    currOneBoxResult.setUrl(contentBuff.ToString())
                    Exit Select
            End Select
        End Sub

        Private Sub doOneBoxResponse(ByVal tag As Integer)
            Select Case tag
                Case XMLTags.OBRES
                    response.addOneBoxResponse(currOneBoxResponse)
                    currOneBoxResponse = New OneBoxResponse()
                    inOneBoxResponse = False
                    Exit Select
            End Select
        End Sub

        Private Sub Emit(ByVal s As String)
            sb.Remove(0, sb.Length)
            sb.Append(s)
        End Sub

#End Region
    End Class

    Public NotInheritable Class EmitObject
        Public Name As String
        Public Expression As String
        Public ReplaceExpression As String

        Public Sub New(ByVal _Name As String, ByVal _Expression As String, ByVal _ReplaceExpression As String)
            Me.Name = _Name
            Me.Expression = _Expression
            Me.ReplaceExpression = _ReplaceExpression
        End Sub
    End Class
End Namespace
